package f2c.net.util;

import java.net.Socket;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;


/**
 * Class to invoke a Web Services
 * @author francesco falanga
 */
public class WSRequestBuilder {

    public WSRequestBuilder() {
    }
    
    public WSRequestBuilder(SoapNSType soaptype) {
        this.soaptype = soaptype;
    }

    private String  server          = "";
    private String  webServicePath  = "";
    private String  soapAction      = "";
    private String  methodName      = null;
    private String  xmlNamespace    = "";
    private String  customHeader    = "";

    private int     webServicePort  = 80;
    //response timeout
    private int     waitSeconds     = 10;

    private SoapNSType soaptype     = SoapNSType.soap;

    private boolean wsseenabled    = false;
    private String  wsseHeader     = "";
    
    private boolean xmlHeaderEnabled    = true;

    //private Hashtable<String, Object> params = new Hashtable<String, Object>();
    //private List<Object> params = new ArrayList<Object>();
    private List<IParamValue> params = new ArrayList<IParamValue>();
    
    private HashMap<String, String> namespace = new HashMap<String, String>();

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getServer() {
        return server;
    }

    public void setServer(String server) {
        this.server = server;
    }

    public String getSoapAction() {
        return soapAction;
    }

    public void setSoapAction(String soapAction) {
        this.soapAction = soapAction;
    }

    public String getWebServicePath() {
        return webServicePath;
    }

    public void setWebServicePath(String webServicePath) {
        this.webServicePath = webServicePath;
    }

    public int getWebServicePort() {
        return webServicePort;
    }

    public void setWebServicePort(int webServicePort) {
        this.webServicePort = webServicePort;
    }

    public String getXmlNamespace() {
        return xmlNamespace;
    }

    public void setXmlNamespace(String xmlNamespace) {
        this.xmlNamespace = xmlNamespace;
    }

    public int getWaitSeconds() {
        return waitSeconds;
    }

    public void setWaitSeconds(int wait_seconds) {
        this.waitSeconds = wait_seconds;
    }

    public void setCustomHeader(String customheader){
        this.customHeader = customheader;
    }

    public String getCustomHeader(){
        return customHeader;
    }
    
    public boolean isXmlHeaderEnabled() {
		return xmlHeaderEnabled;
	}

	public void setXmlHeaderEnabled(boolean xmlHeaderEnabled) {
		this.xmlHeaderEnabled = xmlHeaderEnabled;
	}

	public ParamValue addParameter(String name, Object data) {
        ParamValue pv = new ParamValue(name, data);
    	params.add(pv);
    	return pv;
    }
    
    public void addComplexType(ComplexType ct){
        params.add(ct);
    }
    
    
    
    public void addNamespace(String name, String url) {
        namespace.put(name, url);
    }

    public void addWsseHeader(String login, String password, boolean mustUnderstand)
    {
        StringBuilder sb = new StringBuilder("<wsse:Security ");

        if(mustUnderstand)
            sb.append(this.soaptype.name()).append(":mustUnderstand=\"1\" " );

        sb.append("xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/07/secext\">")
        .append("<wsse:UsernameToken>")
        .append("<wsse:Username>%s</wsse:Username>")
        .append("<wsse:Password Type=\"wsse:PasswordText\">%s</wsse:Password>")
        .append("</wsse:UsernameToken>")
        .append("</wsse:Security>");

        wsseHeader = String.format(sb.toString(), login, password);
        wsseenabled = true;
    }

    
    public void addWsseCustomHeader(ComplexType ct)
    {
    	wsseHeader += String.format(ct.getSoap());
        wsseenabled = true;
    }
    
    
    public void removeWsseHeader()
    {
        wsseHeader = "";
        wsseenabled = false;
    }
    
  /**
   * costruisce il messaggio SOAP per chiamare un metodo che accetta n parametri
   * @return String
   */
    public String getSoapMessage() throws UnsupportedEncodingException {
    	StringBuffer sb = new StringBuffer("");
    	
    	if(xmlHeaderEnabled)
    		sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");    	

        sb.append("<").append(soaptype.name()).append(":Envelope ");
        
        //xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"
        for(String key : namespace.keySet()){
            sb.append(String.format("xmlns:%s=\"%s\" ", key, namespace.get(key)));
        }
        sb.append(">");

        //Buil HEADER
        sb.append("<").append(soaptype.name()).append(":Header>");
        //add standard wsse security
        if(wsseenabled)
        {
            sb.append(wsseHeader);
        }
        sb.append(customHeader);
        sb.append("</").append(soaptype.name()).append(":Header>");

        if(methodName != null)
        {
        //Build BODY
        sb.append("<").append(soaptype.name()).append(":Body>");
        //sb.append("<" + methodName + " xmlns=\"" + xmlNamespace + "\">");
        sb.append("<").append(methodName).append(">");
        //Parameters passed to the method are added here
        /*for(String name : params.keySet())
        {
            Object objdata = params.get(name);
            sb.append(getSoapParams(name, objdata));           
        }
        */
        for(IParamValue objdata : params)
        {
        	sb.append(getSoapParams(objdata));
        }
          
        sb.append("</").append(methodName).append(">");
        
        sb.append("</").append(soaptype.name()).append(":Body>");
        }
        else
        	sb.append("<").append(soaptype.name()).append(":Body/>");
        
        sb.append("</").append(soaptype.name()).append(":Envelope>");
        
        return sb.toString();
      }

    public String sendRequestGetParam(String paramname) throws Exception {
        String sresult = sendRequest();
        int start = sresult.indexOf("<" + paramname + ">") +
          paramname.length() + 2;
        int end = sresult.indexOf("</" + paramname + ">");
        return sresult.substring(start, end);
    }

    public Map<String, List<String>> sendRequestGetParams(String[] keys) throws Exception {
        String sresult = sendRequest();

        Map<String,List<String>> keymap = null;

        for(String key : keys){
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = factory.newDocumentBuilder();

            Document document;
            document =parser.parse(new InputSource(new StringReader(sresult)));

            keymap = new HashMap<String,List<String>>();            
        for(String keyi : keys){
            NodeList list = document.getElementsByTagName(keyi);
            List<String> values=new ArrayList<String>();
            for (int i=0;i<list.getLength();i++){
                Node n = list.item(i);
                if(n!=null && n.getTextContent()!=null)
                    values.add(n.getTextContent());
                }
                keymap.put(keyi,values);
            }
        }
        return keymap;
    }
      /**
       * call the Web Services and return the response
       * @return String
       */
      public String sendRequest() throws Exception{
        String retval = "";
        Socket socket = null;
        try {
          socket = new Socket(server, webServicePort);
        }
        catch (Exception ex1) {
            ex1.printStackTrace();
          return ("Error: "+ ex1.getMessage());
        }

        try {
          PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
          String smessageSOAP = getSoapMessage();
          // send an HTTP request to the web service
          out.println("POST " + webServicePath + " HTTP/1.1");
          out.println("Host: " + server + ":" + webServicePort);
          out.println("Content-Type: text/xml; charset=utf-8"); // utf-8
          out.println("Content-Length: " + String.valueOf(smessageSOAP.length()));
          out.println("SOAPAction: \"" + soapAction + "\"");
          out.println("Connection: Close");
          out.println();
          out.print(smessageSOAP);
          out.flush();

        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String inputLine;
        StringBuffer sb = new StringBuffer(1000);

        boolean timeout = false;
        long m = System.currentTimeMillis();

        while ( (inputLine = in.readLine()) != null && !timeout) {
          sb.append(inputLine + "\n");
          if ( (System.currentTimeMillis() - m) > (1000 * waitSeconds)) timeout = true;
        }
        in.close();

        if (!timeout) {
          retval = sb.toString();
        }
        else {
          retval = "Error: response timed out.";
        }
        socket.close();
      }
      catch (Exception ex) {
        throw  new Exception("Error: cannot communicate.", ex);
      }
      return getDecodingXMLUnicode(retval);
      }



      /**
       * wake-up the web services (if needed)
       * @return boolean
       */
      public boolean ping() {
      try {
         //Create socket
         Socket sock = new Socket(server, webServicePort);

         //Send header
         PrintWriter out = new PrintWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8"), true);
         out.println("GET " + webServicePath + " HTTP/1.1");
         out.println("Host: " + server);
         out.println();
         out.flush();

         // Response
         BufferedReader rd = new BufferedReader(new InputStreamReader(sock.getInputStream()));

         String buf = "";
         int size = -1;
         boolean flag = false;
         int count = 0;
         while(true) { // legge risposta carattere x carattere
            int i = rd.read();
            if(i != -1) {
               char c = (char)i;
               if(c == '\r') { // fine linea \r\n
                  if(buf.toLowerCase().startsWith("content-length: ")) { // propriet�unghezza corpo messaggio
                     flag = true;
                     size = Integer.parseInt(buf.substring(new String("Content-length: ").length()));
                  }
                  buf = "";
               } else { // altro carattere
                  if(c != '\n') { // scarta secondo carattere fine linea
                     buf += c;
                     if(flag) count++;
                     if(count == size) { // raggiunto numero caratteri lunghezza corpo messaggio
                        sock.close();
                        break;
                     } else if(buf.indexOf("</html>") != -1) { // fine pagina
                        sock.close();
                        break;
                     }
                  }
               }
            } else {
               break;
            }
         }
         return true;
      } catch(IOException ex) {
          ex.printStackTrace();
         return false;
      }

   }

   /**
    * Convert the characters from unicode to UTF8 and < > in html
    * @param svalue String
    * @return String
    */
   public static String getEncodingXMLUTF8(String svalue)
   {
     String sret = "";
     try{
       sret = svalue.replaceAll("<", "&lt;");
       sret = sret.replaceAll(">", "&gt;");
       sret = new String(sret.getBytes("UTF8"));
     }catch(Exception ex)
     {
         ex.printStackTrace();
     }
     return sret;
   }

   /**
    * Convert the character from UTF8 to unicode
    * @param svalue
    * @return
    */
   public static String getDecodingXMLUnicode(String svalue)
     {
       String sret = "";
       try{
         sret = svalue.replaceAll("&lt;", "<");
         sret = sret.replaceAll("&gt;", ">");
         sret = new String(sret.getBytes(),  "UTF-8");
       }catch(Exception ex)
       {
           ex.printStackTrace();
       }
       return sret;
   }

   /**
    * Class to handle complex type (class) as parameters in web services signature
    * It allow to send class as parameter in web-services invoking
    * example :
    * //ws signature
    * myServices(String value, Resource res)
    * //parameter in signature
    * class Resource{ String id; String name; String description;}
    * 
    * @return ComplexType
    */
   public ComplexType getComplexType(String name)
   {
     return new ComplexType(name);
   }

   /**
    * Utility 
    * @param paramname
    * @param data
    * @return
    * @throws UnsupportedEncodingException
    */
   private static String getSoapParams(IParamValue data)
   {
	   return data.getSoap();
	   /*
        if( data instanceof ComplexType)
        {
            return String.format("<%s>%s</%s>", ((ComplexType)data).getName(), ((ComplexType)data).getSOAPFormat(), ((ComplexType)data).getName());
        }
        else
        {
        	if(data instanceof ParamValue)
        		return getSoapParam( ((ParamValue) data).getName() , data);
        	else
        		return null;
        }
        */        
   }

   /**
    * Utility
    * @param paramname
    * @param data
    * @return
    */
   private static String getSoapParam(String paramname, Object data)
   {
       String _utf8 = getEncodingXMLUTF8((data.toString()));
       return String.format("<%s>%s</%s>", paramname, _utf8, paramname);
   }

   private static String getSoapParam(String paramname, Object data, String attributes)
   {
       String _utf8 = getEncodingXMLUTF8((data.toString()));
       return String.format("<%s %s>%s</%s>", paramname, attributes, _utf8, paramname);
   }
   
   public class ComplexType implements IParamValue
   {
     private List<IParamValue> ct_elements = new ArrayList<IParamValue>();
     
     private String name;
     private String attributes;
     
     private ComplexType(String name) throws IllegalArgumentException
     {    	 
    	 if((name == null) || (name.equals("")))
    		 throw new IllegalArgumentException("The name can't be null or empty");
    	 this.name = name;
     }

     private ComplexType(String name, String attributes) throws IllegalArgumentException
     {    	 
    	 this(name);
    	 this.attributes = attributes;
     }

     public String getName()
     {
    	 return name;
     }
          
     public String getAttributes() {
		return attributes;
	}

	public void setAttributes(String attributes) {
		this.attributes = attributes;
	}

	public ParamValue addParam(String name, Object data) {
         ParamValue pv = new ParamValue(name, data);
         ct_elements.add(pv);
     	return pv;
     }
     
     public ParamValue addParam(String name, Object data, String attributes) {
         ParamValue pv = new ParamValue(name, data, attributes);
         ct_elements.add(pv);
     	return pv;
     }
     public void addComplexType(ComplexType ct){
    	 ct_elements.add(ct);
     }
     
     public String getSOAPFormat() {
    	return getSoap();
     }

	public String getSoap() {
		StringBuffer sb = new StringBuffer();
	       sb.append("<")
	       .append(this.getName());
	       if(attributes != null)
	    	   sb.append(" ").append(attributes).append(" ");
	       sb.append(">");
	       for(IParamValue objdata : ct_elements)
	       {
	           sb.append(objdata.getSoap());           
	       }
	       sb.append("</")
	       .append(this.getName())
	       .append(">");
	       return sb.toString();		
	}

   }
   
   public class ParamValue implements IParamValue
   {
     private String name;
     private Object value;
     private String attributes; 
     
     ParamValue(String name, Object value)
     {
    	 this.name = name;
    	 this.value = value;
     }
    
     ParamValue(String name, Object value, String attributes)
     {
    	 this.name = name;
    	 this.value = value;
    	 this.attributes = attributes;
     }
	
     
	public Object getValue() {
		return value;
	}

	public void setValue(Object value) {
		this.value = value;
	}

	public String getName() {
		return name;
	}
	
	public String getAttributes() {
		return attributes;
	}

	public void setAttributes(String attributes) {
		this.attributes = attributes;
	}

	public String getSoap() {
		if(null != attributes)
			return WSRequestBuilder.getSoapParam(name, value, attributes);
		else
			return WSRequestBuilder.getSoapParam(name, value);
	}     
     
   }
   

}
